/**
 * 
 */
package org.bookie.framework;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.PageRedirectException;
import org.apache.tapestry.annotations.InitialValue;
import org.apache.tapestry.annotations.InjectObject;
import org.apache.tapestry.annotations.Persist;
import org.apache.tapestry.engine.IEngineService;
import org.apache.tapestry.event.PageAttachListener;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.form.IFormComponent;
import org.apache.tapestry.html.BasePage;
import org.apache.tapestry.services.ResponseBuilder;
import org.apache.tapestry.valid.IValidationDelegate;
import org.apache.tapestry.valid.ValidationConstraint;
import org.bookie.core.services.IAuthenticationContext;
import org.bookie.framework.services.NotificationService;
import org.bookie.model.Role;
import org.bookie.model.security.Access;

/**
 * Common class for all Bookie pages. Implements some generic functionality:
 * <li>Instantiate custom delegate</li>
 * 
 * @author zubairov
 * 
 */
public abstract class CommonBasePage extends BasePage  implements PageAttachListener {

	protected Log log = LogFactory.getLog(getClass());
	
	/**
	 * Setter for the delegate
	 * 
	 * @category injected
	 * @param delegate
	 */
	@InitialValue("new org.bookie.framework.CustomDelegate()")
	public abstract void setDelegate(IValidationDelegate delegate);

	/**
	 * Getter for the delegate
	 * 
	 * @return
	 */
	public abstract IValidationDelegate getDelegate();

	/**
	 * Returns an page engine service
	 * 
	 * @return
	 */
	@InjectObject("engine-service:page")
	public abstract IEngineService getPageService();
	
	/**
	 * Get the external service
	 * @return
	 */
	@InjectObject("engine-service:external")
	public abstract IEngineService getExternalService();
	
	/**
	 * Get the direct service
	 * @return
	 */
	@InjectObject("engine-service:direct")
	public abstract IEngineService getDirectService();

	/**
	 * Injected response builder
	 * @return
	 */
	@InjectObject("service:tapestry.globals.ResponseBuilder")
	public abstract ResponseBuilder getResponseBuilder();
	
	/**
	 * Returns injected {@link NotificationService}
	 * @return
	 */
	@InjectObject("service:bookie.framework.NotificationService")
	public abstract NotificationService getNotificationService();
	
	
	/**
	 * Propery with flash persistence
	 * 
	 * @param message message to persist
	 */
	@Persist("flash")
	public abstract void setMessage(String message);

	/**
	 * Getter for the message
	 * 
	 * @return
	 */
	public abstract String getMessage();
	/**
	 * Injected authentication context.
	 * @return instance of IAuthenticationContext
	 */
	@InjectObject("service:bookie.core.AuthenticationContext")
	public abstract IAuthenticationContext getAuthenticationContext();

	/**
	 * Recods errror to the delegate with given error message for any form
	 * component association with {@link ValidationConstraint#CONSISTENCY}
	 * 
	 * @param messageKey
	 * @param strings possible component ids
	 */
	protected void recordError(String messageKey, String... strings) {
		recordError(messageKey, ValidationConstraint.CONSISTENCY, strings);
	}
	
	/**
	 * Recods errror to the delegate with given error message, without any form
	 * component association with an validation constraint
	 * 
	 * @param messageKey key of the message in the properties
	 * @param constraint validation constraint
	 * @param strings possible component ids
	 */
	protected void recordError(String messageKey, ValidationConstraint constraint, String... strings) {
		if (strings.length == 1) {
			getDelegate().setFormComponent((IFormComponent) getComponent(strings[0]));
		} else {
			getDelegate().setFormComponent(null);		
		}
		String message = getMessages().getMessage(messageKey);
		getDelegate().record(message, constraint);
	}
	
	/**
	 * Format a string using {@link #getMessages()}
	 * @param key the key used to obtain a localized pattern
	 * @param argument1 first argument passed to the formatter
	 */
	public void formatError(String key, String argument1){
		getDelegate().setFormComponent(null);
		String message = getMessages().format(key, argument1);
		getDelegate().record(message, ValidationConstraint.CONSISTENCY);
	}
	
	/**
	 * @see org.apache.tapestry.AbstractPage#validate(org.apache.tapestry.IRequestCycle)
	 */
	@Override
	public void validate(IRequestCycle cycle) {
		super.validate(cycle);
		validateAccess();
	}
	
	/**
	 * Validates the acces to the page. The pages are protected by the annotation
	 * {@link Access}. This method checks whether an annotation restricts the access
	 * to the page to some {@link Role}s. If the current User is not in the required Role,
	 * the access is not granted.
	 */
	protected void validateAccess() {
		if(!getAuthenticationContext().isUserInRole(getRole())){
			throw new PageRedirectException("AuthenticationError");
		}
	}
	
	/**
	 * Get the {@link Role} of the {@link Access} of the page.
	 * If the page was not annotated by {@link Access}, Role.GUEST is returned.
	 * @return instance of Role
	 */
	@SuppressWarnings("unchecked")
	public Role getRole(){
		Class clazz = getPageClass();
		if(clazz.isAnnotationPresent(Access.class)){
			Access access = (Access) clazz.getAnnotation(Access.class);
			if(null!=access){
				return access.role();
			}
		}
		return Role.GUEST;
	}
	
	/**
	 * Get the real class of the page without any tapestry enhancements.
	 * @return class of the page
	 */
	private Class getPageClass(){
		Class clazz = getClass();
		return clazz.getSuperclass();
	}
	
	/**
	 * Updates the given components
	 * @param components components to update
	 */
	public void updateComponents(String... components){
		for(String id:components){
			getResponseBuilder().updateComponent(id);
		}
	}
	
	/**
	 * When page where component is located activated
	 * we need to register it's methods for notification
	 * 
	 */
	public void pageAttached(PageEvent event) {
		log.debug("Registering component " + getIdPath() + " in the listener service");
		getNotificationService().registerListener(this);
	}

}
